"""
Parser for expressions like 1 + 20 * e

Supporting operators:
    ()
    ||
    &&
    + -
    * / %
    |
    & ^
    ~ ! - + (unar)
"""
from S3D.Parser import Parser, ParserData
from unittest import TestCase

class Expr(ParserData):
    # type
    BIN_OP  = 0
    # op, left, right
    UNAR_OP = 1
    # op, value
    SUBNAME = 2
    # value, name
    CALL    = 3
    # value, args
    INDEX   = 4
    # value, args
    VAR     = 5
    # name
    VALUE   = 6
    # value     # string, int or float

    def __init__(self, type, **kw):
        self.type = type
        for k, v in kw.items():
            setattr(self, k, v)

    def __eq__(self, p):
        if self.type != p.type:
            return False
        for v in dir(self):
            if not v.startswith('_'):
                if getattr(self, v) != getattr(p, v):
                    return False

        return True

    @staticmethod
    def makeBinOp(op, left, right): 
        return Expr(Expr.BIN_OP, op = op, left = left, right = right)

    @staticmethod
    def makeUnarOp(op, value):
        return Expr(Expr.UNAR_OP, op = op, value = value)

    @staticmethod
    def makeSubname(value, name):
        return Expr(Expr.SUBNAME, value = value, name = name)

    @staticmethod
    def makeCall(value, args):
        return Expr(Expr.CALL, value = value, args = args)

    @staticmethod
    def makeIndex(value, args):
        return Expr(Expr.INDEX, value = value, args = args)

    @staticmethod
    def makeVar(name):
        return Expr(Expr.VAR, name = name)

    @staticmethod
    def makeValue(value):
        return Expr(Expr.VALUE, value = value)

    def __repr__(self):
        if self.type == Expr.BIN_OP:
            return '(%s %s %s)' % (self.left, self.op, self.right)
        if self.type == Expr.UNAR_OP:
            return '%s%s' % (self.op, self.value)
        if self.type == Expr.SUBNAME:
            return '%s.%s' % (self.value, self.name)
        if self.type == Expr.CALL:
            return '%s%s' % (self.value, '(%s)' % ', '.join([str(p) for p in self.args]))
        if self.type == Expr.INDEX:
            return '%s%s' % (self.value, self.args)
        if self.type == Expr.VAR:
            return self.name
        if self.type == Expr.VALUE:
            return str(self.value)

        for name in dir(self):
            if getattr(self, name) is self.type:
                return "<Unknown expr type: '%s'>" % name

class ExprCompiler(Parser):
    _BIN_OP = [
        ['||'],
        ['&&'],
        ['+', '-'],
        ['*', '/', '%'],
        ['|'],
        ['&', '^'],
    ]

    _UNAR_OP = [ '~', '!', '-', '+' ]

    def __init__(
        self,
        supportSubname      = True,
        supportCalls        = True,
        supportIndex        = True,
        supportMultiIndex   = True,
        supportVariables    = True
    ):
        self.supportSubname     = supportSubname
        self.supportCalls       = supportCalls
        self.supportIndex       = supportIndex
        self.supportMultiIndex  = supportMultiIndex
        self.supportVariables   = supportVariables

    def parse(self):
        r = self.parseExpr()

        if not self.isNestedCompile and not self.nextIsToken('eof'):
            self.matchNothing()

        return r

    def parseExpr(self):
        valStack = [self.parseUnar()]
        opStack = []

        while True:
            nextLevel = -1
            nextOp = None
            for level, op_list in enumerate(ExprCompiler._BIN_OP):
                for op in op_list:
                    if self.nextIsOperator(op, checkSingle = True):
                        nextLevel   = level
                        nextOp      = op
                        break
                if nextLevel != -1:
                    break

            if nextOp is None:
                while len(valStack) > 1:
                    valStack[-2] = Expr.makeBinOp(opStack[-1][0], valStack[-2], valStack[-1])
                    valStack.pop()
                    opStack.pop()
                break

            self.matchOperator(nextOp)

            if len(opStack) > 0:
                lastOp, lastLevel = opStack[-1]
                while lastLevel >= nextLevel:
                    valStack[-2] = Expr.makeBinOp(lastOp, valStack[-2], valStack[-1])
                    valStack.pop()
                    opStack.pop()

                    if len(opStack) > 0:
                        lastOp, lastLevel = opStack[-1]
                    else:
                        break

            opStack.append((nextOp, nextLevel))
            valStack.append(self.parseUnar())

        return valStack[0]

    def parseUnar(self):
        if self.nextIsSymbolList(ExprCompiler._UNAR_OP):
            op = self.matchAny()
            return Expr.makeUnarOp(op, self.parseUnar())

        return self.parsePost()

    def parsePost(self):
        r = self.parseValue()

        while True:
            if self.supportSubname and self.tryMatchSymbol('.'):
                name = self.matchWord()
                r = Expr.makeSubname(r, name)
            elif self.supportCalls and self.tryMatchSymbol('('):
                args = []
                if self.tryMatchSymbol(')'):
                    r = Expr.makeCall(r, args)
                    break

                args.append(self.parseExpr())

                while not self.tryMatchSymbol(')'):
                    self.matchSymbol(',')
                    args.append(self.parseExpr())

                r = Expr.makeCall(r, args)
            elif self.supportIndex and self.tryMatchSymbol('['):
                args = [self.parseExpr()]
                if self.supportMultiIndex:
                    while not self.tryMatchSymbol(']'):
                        self.matchSymbol(',')
                        args.append(self.parseExpr())
                else:
                    self.matchSymbol(']')

                r = Expr.makeIndex(r, args)
            else:
                break

        return r

    def parseValue(self):
        if self.tryMatchSymbol('('):
            r = self.parseExpr()
            self.matchSymbol(')')
            return r

        if self.nextIsWord():
            if not self.supportVariables:
                self.matchNothing()

            return Expr.makeVar(self.matchWord())

        return Expr.makeValue(self.matchValue())

class Tests(TestCase):
    def setUp(self):
        self.c = ExprCompiler()

    def check(self, expr, res):
        r = self.c.compile(expr)
        self.assertIsNotNone(r, self.c.errorMsg)
        self.assertEqual(str(r), res)

    def testSimple(self):
        self.check('simpleValue', 'simpleValue')

    def testAdd(self):
        self.check('a + b', '(a + b)')
        self.check('a + b + c', '((a + b) + c)')

    def testSameLevel(self):
        self.check('1 + 2 - 3', '((1 + 2) - 3)')
        self.check('1 - a - c + a', '(((1 - a) - c) + a)')
        self.check('1 / a * b', '((1 / a) * b)')

    def testAddMul(self):
        self.check('a + b * c',             '(a + (b * c))')
        self.check('a + b * c / d',         '(a + ((b * c) / d))')
        self.check('a + b * c & d - 10',    '((a + (b * (c & d))) - 10)')
        self.check('1 + b & c | x / 10',    '(1 + (((b & c) | x) / 10))')
        self.check('1 + x | b & c / 10',    '(1 + ((x | (b & c)) / 10))')

    def testSubname(self):
        self.check('a.b', 'a.b')
        self.check('a.b + c * d.e.f', '(a.b + (c * d.e.f))')

    def testCall(self):
        self.check('a(10, b.mmm + c, d - f / k)', 'a(10, (b.mmm + c), (d - (f / k)))')
        self.check('a() + b(10 + 5) * g(h(k, 1), a)', '(a() + (b((10 + 5)) * g(h(k, 1), a)))')

    def testIndex(self):
        self.check('value[10, a()]', 'value[10, a()]')
        self.check('val[index + 3] * 10', '(val[(index + 3)] * 10)')

    def testExprOps(self):
        self.assertTrue(Expr.makeValue(10) == Expr.makeValue(10))
        self.assertTrue(Expr.makeValue(10) != Expr.makeValue(11))
        self.assertTrue(Expr.makeValue(10) != Expr.makeVar('test'))